手把手教你vite+elementPlus+vue3+jsplumb+pinia+sortable实现完整的流程图 您所在的位置:网站首页 canvas 流程图 手把手教你vite+elementPlus+vue3+jsplumb+pinia+sortable实现完整的流程图

手把手教你vite+elementPlus+vue3+jsplumb+pinia+sortable实现完整的流程图

2023-05-20 13:32| 来源: 网络整理| 查看: 265

1、vite创建项目 yarn create vite

image.png

2、安装vue-router npm install vue-router 1、新建一个router的文件夹 2、在文件夹下面创建一个index.js文件 3、main.js进行挂载 index.js import { createRouter, createWebHistory } from 'vue-router' import home from '../view/home.vue' const routerHistory = createWebHistory() const router = createRouter({ history: routerHistory, routes: [ { path: '/', redirect:'home' }, { path: '/home', name:'home', component: home }, ] }) export default router; main.js挂在router import { createApp } from 'vue' import App from './App.vue' //路由 import router from './router' createApp(App).use(router).use(store).use(ElementPlus).mount('#app') app.vue改造 3、安装pinia npm i pinia -S 1、新建 src/store 目录并在其下面创建 index.js,导出 store import { createPinia } from 'pinia'  const store = createPinia()  export default store 2、在 main.js 中引入并使用 import { createApp } from 'vue' import App from './App.vue' import store from './store' import router from "./router"; createApp(App).use(router).use(store).mount('#app') 3、在 src/store 下面创建一个 confgJsplumb.js import { defineStore } from 'pinia'  export const configJsplumb = defineStore({    id: 'confgJsplumb', // id必填,且需要唯一    state: () => {      return {        name: '张三'     }   },    actions: {      updateName(name) {        this.name = name     }   }  }) 4、获取State {{configJsplumbData.name}} import { configJsplumb } from '../store/configJsplumb.js' let configJsplumbData = configJsplumb() console.log(configJsplumbData.name) 5、修改值 {{configJsplumbData.name}} import { configJsplumb } from '../store/configJsplumb.js' let configJsplumbData = configJsplumb() configJsplumbData.updateName('李四') 4、安装less或者scss yarn add -D sass npm install sass -D yarn add -D less npm install less -D 5、集成eslint和prettier npm install --save-dev eslint eslint-config-prettier eslint-plugin-prettier eslint-plugin-vue eslint-plugin-html prettier @babel/plugin-syntax-dynamic-import babel-eslint 创建配置文件: `.eslintrc.js` module.exports = { root: true, env: { node: true, 'vue/setup-compiler-macros': true, }, extends: [ 'eslint:recommended', 'plugin:vue/vue3-recommended', 'prettier', 'plugin:prettier/recommended', ], plugins: ['vue', 'html', 'prettier'], parserOptions: { ecmaVersion: 8, }, rules: { 'prettier/prettier': 'error', 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'vue/no-multiple-template-root': 'off', 'vue/multi-word-component-names': 'off', 'no-mutating-props': 'off', 'vue/no-v-html': 'off', }, overrides: [ { files: [ '**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)', ], env: { mocha: true, }, }, ], }; 创建忽略文件:`.eslintignore` node_modules/ dist/ index.html 创建配置文件: `prettier.config.js` 或 `.prettierrc.js` module.exports = { // 一行最多 80 字符 printWidth: 80, // 使用 4 个空格缩进 tabWidth: 4, // 不使用 tab 缩进,而使用空格 useTabs: false, // 行尾需要有分号 semi: true, // 使用单引号代替双引号 singleQuote: true, // 对象的 key 仅在必要时用引号 quoteProps: 'as-needed', // jsx 不使用单引号,而使用双引号 jsxSingleQuote: false, // 末尾使用逗号 trailingComma: 'all', // 大括号内的首尾需要空格 { foo: bar } bracketSpacing: true, // jsx 标签的反尖括号需要换行 jsxBracketSameLine: false, // 箭头函数,只有一个参数的时候,也需要括号 arrowParens: 'always', // 每个文件格式化的范围是文件的全部内容 rangeStart: 0, rangeEnd: Infinity, // 不需要写文件开头的 @prettier requirePragma: false, // 不需要自动在文件开头插入 @prettier insertPragma: false, // 使用默认的折行标准 proseWrap: 'preserve', // 根据显示样式决定 html 要不要折行 htmlWhitespaceSensitivity: 'css', // 换行符使用 lf endOfLine: 'auto' } 6、安装eslint错误显示信息依赖 vite-plugin-eslint vite.config.js修改 import { defineConfig } from 'vite'; import eslintPlugin from 'vite-plugin-eslint'; export default defineConfig({ plugins: [eslintPlugin()], }); 7、安装element-plus npm install --save element-plus import { createApp } from 'vue'; import App from './App.vue'; import store from './store'; import router from './router'; import ElementPlus from 'element-plus'; import 'element-plus/dist/index.css'; createApp(App).use(router).use(store).use(ElementPlus).mount('#app'); 8、流程图整体功能和实现效果以及代码结构图

image.png

image.png

9、安装sortable实现左侧列表拖拽 npm install sortablejs --save //代码演示 //left.vue {{ item.name }} export default { name: 'Left', }; import { onMounted } from 'vue'; import Sortable from 'sortablejs'; import { configJsplumb } from '../../store/configJsplumb.js'; let tableData = [ { type: 'statr', name: '开始', color: '#479A52', }, { type: 'node', name: '节点', color: '#5a9cf8', }, { type: 'end', name: '结束', color: 'red', }, ]; let configJsplumbData = configJsplumb(); onMounted(() => { // 第一步,获取行容器 let dome = document.getElementById('leftUl'); // 第二步,初始化,给容器指定对应拖拽规则 new Sortable(dome, { group: 'shared', animation: 150, sort: false, //关闭在盒内可以拖拽 forceFallback: true,//忽略 HTML5拖拽行为,强制回调进行 ghostClass: 'blue-background-class', //drop placeholder的css类名 //结束拖拽 onEnd: function (evt) { //当元素移动的距离左侧小于309px的时候并且距离顶部的距离小于69px的时候,禁止插入 if (evt.originalEvent.x < 309 && evt.originalEvent.y < 69) return; let list = { id: setSessionid(),//生成随机Id x: evt.originalEvent.x - 310,//落下的位置(310是左侧列表整体的宽度) y: evt.originalEvent.y - 70,//落下的位置(70是头部的宽度) }; //根据当前数据的下标拿到对应数据的,并和list合并 Object.assign(list, tableData[evt.newIndex]); //并使用pinia来存储数据 configJsplumbData.updateSortData(list); }, }); }); //生成随机id const setSessionid = () => { var len = 32; var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; var maxPos = chars.length; var pwd = ''; for (var i = 0; i < len; i++) { pwd += chars.charAt(Math.floor(Math.random() * maxPos)); } return pwd; }; #leftUl { height: 100%; box-sizing: border-box; padding: 0 5px; list-style: none; li { text-align: center; padding: 5px 10px; border: 1px solid #eaeaea; margin-bottom: 3px; font-size: 14px; cursor: pointer; background: white; &:first-child { margin-top: 3px; } } } 10、pinia里面的代码 import { defineStore } from 'pinia'; export const configJsplumb = defineStore({ id: 'configJsplumb', // id必填,且需要唯一 state: () => { return { jslumbSortData: [], }; }, actions: { //拿到数据就插入,假如大家要做一些插入的时候,判断是否重复就可以在这里做 updateSortData(name) { this.jslumbSortData.unshift(name); }, }, }); 11、引入jsplumb npm install --save jsplumb //引入 import {ref, reactive,onMounted} from 'vue' import jsPlumb from 'jsplumb' 12、右键菜单工具 npm i contextmenu-vue@next

image.png

const handleContextMenu = ($event) => { $contextmenu({ x: $event.x, y: $event.y, customLayoutClass: 'customLayoutClass', menuList: [ { text: '删除', icon: 'DeleteFilled', onClick: () => {}, }, { text: '复制', icon: 'DocumentCopy', onClick: () => {}, }, { text: '多选删除', icon: 'IceCream', onClick: () => {}, }, { text: '多选复制', icon: 'Link', onClick: () => {}, }, { text: '多选拖拽', icon: 'Location', onClick: () => {}, }, ], }); }; //右键菜单消失 const rightClick = () => { document.querySelectorAll('.customLayoutClass')[0].style.display = 'none'; }; 13、编写jsplumb配置规则 1、新建一个unti文件夹 2、创建commonConfig.js文件 export const jsplumbSetting = { grid: [10, 10], // 动态锚点、位置自适应 Anchors: ['TopCenter', 'RightMiddle', 'BottomCenter', 'LeftMiddle'], Container: 'center', //可拖动区域 ID // 连线的样式 StateMachine、Flowchart,有四种默认类型:Bezier(贝塞尔曲线),Straight(直线), Flowchart(流程图),State machine(状态机) Connector: [ 'Flowchart', { cornerRadius: 5, alwaysRespectStubs: true, stub: 5 }, ], // 鼠标不能拖动删除线 ConnectionsDetachable: true, // 删除线的时候节点不删除 DeleteEndpointsOnDetach: false, // 连线的端点类型(Dot 圆点、Rectangle 矩形、Image 图像、Blank 空白) // Endpoint: ["Dot", {radius: 5}], Endpoint: [ 'Rectangle', { height: 10, width: 10, }, ], // 线端点的样式 EndpointStyle: { fill: 'red', //端点背景色 outlineWidth: 1, //端点宽度 outlineStroke: 'red', //端点颜色 radius: 5, }, //这个是控制连线终端那个小点的样式 EndpointHoverStyle: { fill: '#fff', outlineStroke: '#13E0F9' }, //这个是控制鼠标滑过连线终端那个小点的样式 LogEnabled: false, //是否打开jsPlumb的内部日志记录 // 绘制线 PaintStyle: { stroke: '#409eff', strokeWidth: 2, //线的宽度 }, HoverPaintStyle: { stroke: '#ff00cc', strokeWidth: 2 }, //鼠标滑过连线的位置 // 绘制箭头 Overlays: [ [ 'Arrow', { width: 8, length: 8, location: 1, }, ], ], RenderMode: 'svg', }; // jsplumb连接参数 export const jsplumbConnectOptions = { isSource: true, isTarget: true, // 动态锚点、提供了4个方向 Continuous、AutoDefault anchor: ['TopCenter', 'RightMiddle', 'BottomCenter', 'LeftMiddle'], }; //开始瞄点 export const jsplumbSourceOptions = { filter: '.node-anchor', //触发连线的区域 /*"span"表示标签,".className"表示类,"#id"表示元素id*/ filterExclude: false, anchor: ['TopCenter', 'RightMiddle', 'BottomCenter', 'LeftMiddle'], allowLoopback: false,//防止往回连接 }; //结束瞄点 export const jsplumbTargetOptions = { filter: '.node-anchor', /*"span"表示标签,".className"表示类,"#id"表示元素id*/ filterExclude: false, anchor: ['TopCenter', 'RightMiddle', 'BottomCenter', 'LeftMiddle'], allowLoopback: false,//防止往回连接 }; 14、初始化jsplumb并引入配置文件 //4个连接点的位置,瞄点 //节点名称 {{ item.name }} export default { name: 'Center', }; import { reactive, toRefs, onMounted, nextTick } from 'vue'; //右键菜单 import { $contextmenu } from 'vue-contextmenu-next/index'; //随机id import { GenNonDuplicateID } from '../../unti/unti.js'; //jsplumbp配置 import { jsplumbSetting, jsplumbConnectOptions, jsplumbSourceOptions, jsplumbTargetOptions, } from '../../unti/commonConfig.js'; //引入jsplumb实例 import jsPlumb from 'jsplumb'; let $jsPlumb = jsPlumb.jsPlumb; // 缓存实例化的jsplumb对象 let jsPlumb_instance = null; // let configJsplumbData = configJsplumb(); let data = reactive({ jsplumbSettingConfig: jsplumbSetting, jsplumbConnectOptionsConfig: jsplumbConnectOptions, jsplumbSourceOptionsConfig: jsplumbSourceOptions, jsplumbTargetOptionsConfig: jsplumbTargetOptions, nodeList: [], lineList: [], }); let { nodeList, //节点数据 lineList, //连线数组 jsplumbTargetOptionsConfig, jsplumbSourceOptionsConfig, jsplumbConnectOptionsConfig, jsplumbSettingConfig, } = toRefs(data); console.log( jsplumbSettingConfig, jsplumbConnectOptionsConfig, jsplumbSourceOptionsConfig, jsplumbTargetOptionsConfig, ); onMounted(() => { //jsplumb默认是注册在浏览器窗口的,将整个页面提供给我们作为一个实例,所以可以使用 getInstance方法建立页面中一个独立的实例: jsPlumb_instance = $jsPlumb.getInstance(); nodeList.value = [ { id: 'Mfzcx4apkafHBYATbdz6hFYntSHsXtDz', x: 335, y: 103, type: 'node', name: 'kaishi1', color: '#5a9cf8', }, { id: 'sGsGhyhEJBsCSmRaAw8xJK3iaefcrKDX', x: 833, y: 79, type: 'statr', name: '开始', color: '#479A52', }, { id: 'sGsGhyhEJBsCSmRaAw8xJK3iaefcrKDl', x: 833, y: 150, type: 'statr', name: '开始', color: '#479A52', }, { id: 'sGsGhyhEJBsCSmRaAw8xJK3iaefcrKDl11', x: 833, y: 300, type: 'statr', name: '开始11111', color: '#479A52', }, ]; nextTick(() => { //因为jsplimb要在dom元素加载之后才能执行,因为他的原理是找到你绑定的画布dom去渲染svg数据,所以必须得画布dom已经渲染之后才能初始化 init(); }); }); //初始化 const init = () => { //ready监听jsplumb是否实例化,当jsplumb实例化执行ready内的方法, jsPlumb_instance.ready(() => { //给jsplumb设置一些默认值 jsPlumb_instance.importDefaults(jsplumbSettingConfig.value); loadEasyFlow(); // 会使整个jsPlumb立即重绘。 jsPlumb_instance.setSuspendDrawing(false, true); }); }; //加载流程图-初始化节点,使节点可以拖拽 const loadEasyFlow = () => { // 初始化节点 for (let i = 0; i < nodeList.value.length; i++) { let node = nodeList.value[i]; // 设置源点,可以拖出线连接其他节点 jsPlumb_instance.makeSource(node.id, jsplumbSourceOptionsConfig.value); // // 设置目标点,其他源点拖出的线可以连接该节点 jsPlumb_instance.makeTarget(node.id, jsplumbTargetOptionsConfig.value); // this.jsPlumb.draggable(node.id); //画布节点添加拖拽方法 draggableNode(node.id); jsPlumb_instance.unbind('connection'); //取消连接事件 for (let i = 0; i < lineList.value.length; i++) { let line = this.data.lineList[i]; jsPlumb_instance.connect( { source: line.from, target: line.to, }, jsplumbConnectOptionsConfig.value, ); } //连线 jsPlumb_instance.bind('connection', (evt) => { let from = evt.source.id; let to = evt.target.id; lineList.value.push({ from: from, to: to, label: '',//连线名称 id: GenNonDuplicateID(8), Remark: '', }); }); } }; //给画布节点添加拖拽方法 const draggableNode = (nodeId) => { console.log(nodeId, 'nodeId'); jsPlumb_instance.draggable(nodeId, { grid: [5, 5], containment: 'center', drag: (event) => { window.event ? (window.event.cancelBubble = true) : event.stopPropagation(); return false; }, stop: (event) => { let nodeIndex = nodeList.value.findIndex((x) => x.id === nodeId); Object.assign(nodeList.value[nodeIndex], { x: event.pos[0], y: event.pos[1], }); }, }); }; //右键菜单 const handleContextMenu = ($event) => { $contextmenu({ x: $event.x, y: $event.y, customLayoutClass: 'customLayoutClass', menuList: [ { text: '删除', icon: 'DeleteFilled', onClick: () => {}, }, { text: '复制', icon: 'DocumentCopy', onClick: () => {}, }, { text: '多选删除', icon: 'IceCream', onClick: () => {}, }, { text: '多选复制', icon: 'Link', onClick: () => {}, }, { text: '多选拖拽', icon: 'Location', onClick: () => {}, }, ], }); }; //右键菜单消失 const rightClick = () => { document.querySelectorAll('.customLayoutClass')[0].style.display = 'none'; }; watch( configJsplumbData.jslumbSortData, () => { if (configJsplumbData.jslumbSortData.length > 0) { let list = configJsplumbData.jslumbSortData[0]; nodeList.value.push(list); //从左侧拖拽添加节点,添加新的节点后,要重绘画布 nextTick(() => { jsPlumb_instance.makeSource( list.id, jsplumbSourceOptionsConfig.value, ); jsPlumb_instance.makeTarget( list.id, jsplumbConnectOptionsConfig.value, ); draggableNode(list.id); }); } }, { deep: true, immediate: true, }, ); #centerRelative { position: relative; width: 100%; height: 100%; .item { position: absolute; text-align: center; padding: 5px 10px; border: 1px solid #eaeaea; width: 180px; height: 20px; font-size: 14px; cursor: pointer; background: white; } } .customLayoutClass { width: 152px !important; .context-menu-item { font-size: 13px; height: 29px; padding: 0; .arrow { .icon { width: 0.7rem; } } } } .node-anchor { width: 10px; height: 10px; border: 1px solid red; z-index: 333; } .anchor-top { position: absolute; top: 0; left: 0; margin-left: 48%; margin-top: -6px; } .anchor-bottom { position: absolute; bottom: 0; left: 0; margin-left: 48%; margin-bottom: -6px; } .anchor-right { position: absolute; right: 0; margin-right: -7px; } .anchor-left { position: absolute; left: 0; margin-left: -7px; } 15、删除节点 jsPlumb_instance.remove(id) //删除 const deleteBtn = (item, index) => { let isState = nodeList.value.some((x) => x.id === item.id); if (isState) { nodeList.value.splice(index, 1); jsPlumb_instance.remove(item.id); rightClick(); return true; } else { return false; } };

image.png image.png

16、复制节点 //复制节点 const copyNodeBtn = (item) => { let list = JSON.parse(JSON.stringify(item)); list.id = GenNonDuplicateID(8); list.x = item.x + 100; nodeList.value.push(list); nextTick(() => { jsPlumb_instance.makeSource(list.id, jsplumbSourceOptionsConfig.value); jsPlumb_instance.makeTarget(list.id, jsplumbConnectOptionsConfig.value); draggableNode(list.id); }); };

image.png 提示:多选复制和多选删除其实和复制节点、删除节点方法一样,暂不做演示,循环与不循环的区别

17、连线前的校验事件 //完成连线前的校验 jsPlumb_instance.bind('beforeDrop', (evt)=>{}) jsPlumb_instance.bind('beforeDrop', (evt) => { //true代表可以连接 false代表可以连接 return beforeDrop(evt); });

image.png true代表可以连接 false代表不可以连接 这里常常一般用于两个节点连接前要做的一些判断

18、连线成功后的事件 jsPlumb_instance.bind('connection', (evt) => {}) jsPlumb_instance.bind('connection', (evt) => { let from = evt.source.id; let to = evt.target.id; lineList.value.push({ from: from, to: to, label: '', id: GenNonDuplicateID(8), Remark: '', }); }); 19、双击连接线 jsPlumb_instance.bind("dblclick",(conn, originalEvent) => {}) jsPlumb_instance.bind("dblclick",(conn, originalEvent) => { console.log(conn,originalEvent) }) 20、单击连接线 jsPlumb_instance.bind('click', (conn, originalEvent)=>{}) jsPlumb_instance.bind('click', (conn, originalEvent) => { console.log(conn, 'conn'); console.log(originalEvent, 'originalEvent'); }); 21、单击删除连接线 jsPlumb_instance.deleteConnection(对象) jsPlumb_instance.bind('click', (conn, originalEvent) => { jsPlumb_instance.deleteConnection(conn); lineList.value.forEach((item, index) => { if (item.from === conn.sourceId && item.to === conn.targetId) { this.data.lineList.splice(index, 1); } }); }); 22、断开连线后事件 jsPlumb_instance.bind('connectionDetached', (evt) => { console.log(evt, '1111'); }); 23、更改连线的样式 .jtk-connector.active { z-index: 9999; path { stroke: #150042; stroke-width: 1.5; animation: ring; animation-duration: 3s; animation-timing-function: linear; animation-iteration-count: infinite; stroke-dasharray: 5; } } @keyframes ring { from { stroke-dashoffset: 50; } to { stroke-dashoffset: 0; } } 24、全局控制连线的样式 // 添加虚线 //获取所有的连线数据 let lines = jsPlumb_instance.getAllConnections(); lines.forEach((line) => { line.canvas.classList.add('active'); }); //关闭虚线 let lines = jsPlumb_instance.getAllConnections(); lines.forEach((line) => { line.canvas.classList.remove('active'); }); 25、给连线加入文字或者html片段 //setLabel jsPlumb_instance.bind('click', (conn, originalEvent) => { conn.setLabel({ label: '122222', cssClass: '',location;'' }); conn.setLabel({ label: '

123213

', cssClass: '' }); console.log(conn, originalEvent); }); 26、获取所有的连线数据 jsPlumb_instance.getConnections() jsPlumb_instance.getAllConnections() getAllConnections(): getAllConnections返回的是浅拷贝的数组,对返回的连线数组进行循环遍历时,当遍历过程中删除了连线时,会破坏数组的循环。解决的办法是将返回的数组进行深拷贝再循环 getConnections(): getConnections返回的是新的数组,所以可以直接循环

image.png

image.png

27、多个节点一起拖拽 jsPlumb_instance.addToDragSelection(id); const chooseMoreDrag = () => { let idArray = nodeList.value.map((x) => x.id); for (let id of idArray) { jsPlumb_instance.addToDragSelection(id); } }; 28、取消多个节点拖拽 jsPlumb_instance.removeFromDragSelection(id); const chooseMoreDrag = () => { let idArray = nodeList.value.map((x) => x.id); for (let id of idArray) { jsPlumb_instance.removeFromDragSelection(id); } }; 29、节点可拖拽 //获取所有节点dom let elem = document.querySelectorAll('.item'); //区域范围内的节点可拖拽 jsPlumb_instance.draggable(elem, true); //节点是否可拖拽 jsPlumb_instance.setDraggable(elem, true); //可直接单独使用setDraggable 30、节点不可拖拽 //获取所有节点dom let elem = document.querySelectorAll('.item'); //区域范围内的节点可拖拽 jsPlumb_instance.draggable(elem, false); //节点是否可拖拽 jsPlumb_instance.setDraggable(elem, false); //可直接单独使用setDraggable 31、清空画布 document.querySelectorAll('画布id').html('') jsPlumb_instance.reset(); 32、删除所有连线 jsPlumb_instance.detachEveryConnection(); 33、vue2版本

juejin.cn/post/706816…

34、git地址

github.com/Caoxiongk/F…

结尾 码字不易,喜欢的小伙伴记得点赞关注


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有